using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Runtime;
using System.Xml;
using System.Reflection;
using System.IO;
using System.Workflow.ComponentModel.Compiler;

namespace EventAbstractionAPI.RuleEngine
{
	public class EventAlgebra: IEventAlgebra
	{

        #region Constructor

        public EventAlgebra()
        {
            XmlReader xomlXmlReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("EventAbstractionAPI.RuleEngine.Workflow1.xoml"));
            _xomlFile.Load(xomlXmlReader);


            XmlReader rulesXmlReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("EventAbstractionAPI.RuleEngine.Workflow1.rules.rules"));
            _rulesFile.Load(rulesXmlReader);

            InitializeWorkFlow();

            _items = new Dictionary<string, object>();

        }

        private void InitializeWorkFlow()
        {
            _workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(WorkflowCompleted);
        } 

        #endregion

        #region IEventAlgebra Members

        public event Fire RaiseEvent;

        /// <summary>
        /// Register a new complex event. 
        /// A complex event is an event comprised of primative events or other complex events.
        /// This method takes the definitions of complex events and turns them into rules for the parser.
        /// The parser inferences rules out of a queue 
        /// </summary>
        /// <param name="complexEvent"></param>
        public void Register(IComplexEvent complexEvent)
        {
        
            _complexEventList.Add(complexEvent);
            
            //if we implement AND then we may need to do it for not only the first event but all events in that set.

            //
            _rulesFile = XMLHelper.AddXML(_rulesFile, "/RuleDefinitions/RuleDefinitions.RuleSets/RuleSet/RuleSet.Rules", complexEvent.Rule);
            //_rulesFile.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("EventAbstractionAPI.RuleEngine.ValidOutput.xml"));

            //if there is a key in our table for this eventType then use it.
            if (_EventTypeExpireTime.ContainsKey(complexEvent.FirstParameter))
            {
                //if we have a value that is greater the use that one instead. Otherwise keep the existing value.
                if (0 > _EventTypeExpireTime[complexEvent.FirstParameter].CompareTo(complexEvent.TimeOfLife))
                {
                    _EventTypeExpireTime[complexEvent.FirstParameter] = complexEvent.TimeOfLife;
                }
            }
            else //if there is no key then make one.
            {
                _EventTypeExpireTime.Add(complexEvent.FirstParameter, complexEvent.TimeOfLife);
            }
        }

        public void Receive(ICapturedEvent capturedEvent)
        {

            capturedEvent.EventTimeStamp = DateTime.Now;
            
            _newQueue.Add(capturedEvent);
            

            if (_workFlowBusy)
            {
                _restartworkflow = true;
            }
            else
            {
                _workFlowBusy = true;
                Expire();
                //the workflow is available to use
                StartWorkflow();
            }

            //TODO: update the timer with the latest _queue list here.
        }

        #endregion

        #region Debug
        
        #if DEBUG

        public event EventHandler WorkFlowStartEvent;
        public event EventHandler WorkFlowEndEvent;

        #endif

        #endregion

        #region Workflow Helpers

        private void StartWorkflow()
        {
            _queue.AddRange(_newQueue);

            #region Debugging Extentions
            #if DEBUG
            try
            {
                WorkFlowStartEvent.Invoke(_queue, null);
            } catch(Exception){}

            #endif
            #endregion

            _newQueue.Clear();
            _items.Remove("Value3");
            _items.Add("Value3", _queue);

            XmlTextReader xomlReader = XMLHelper.getDocumentReader(_xomlFile);
            xomlReader.ResetState();
            XmlTextReader rulesReader = XMLHelper.getDocumentReader(_rulesFile);
            rulesReader.ResetState();

            try 
	        {	        
        	    _workflowInstance = _workflowRuntime.CreateWorkflow(xomlReader , rulesReader, _items);
                _workflowInstance.Start();	
	        }
	        catch (WorkflowValidationFailedException exp)
            {
                StringBuilder errors = new StringBuilder();

                foreach (ValidationError error in exp.Errors)
                {
                    errors.AppendLine(error.ToString());
                }
                Console.WriteLine("Workflow Validation Errors: " + errors.ToString() );
            }
        }

        /// <summary>
        /// When the workflow is completed 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
        {
            #region Debugging Extentions
            #if DEBUG
            
            try
            {
                WorkFlowEndEvent.Invoke(_queue, null);
            } catch(Exception){}

            #endif
            #endregion

            Expire();

            if (_restartworkflow == true)
            {
                _restartworkflow = false;
                StartWorkflow();
            }
            else
            {
                _workFlowBusy = false;
            }
        } 

        #endregion

        #region Timer Helpers

        private void Expire()
        {
            int index = -1;

            for (int i = _queue.Count-1; i >= 0; i--)
            {
                if (_queue[i].EventTimeStamp + GetLifeTime(_queue[i]) < DateTime.Now)
                {
                    index = i;

                    //we only want to find the latest item to expire. consider all elements before this to have expired so break.
                    break;
                }
            }

            //if an event has expired (ie the value of index is no longer -1) then raise an event popping the appropriate items.
            if (index != -1)
            {
                //pop all items that are below or equal to the index number to the event
                List<ICapturedEvent> expiredItems = _queue.GetRange(0,index+1);
                _queue.RemoveRange(0,index+1);
                RaiseEvent.Invoke(expiredItems);
            }
        }

        private TimeSpan GetLifeTime(ICapturedEvent capturedEvent)
        {
            //check our hashdictionary to see if have an expirery set for this event.
            if (_EventTypeExpireTime.ContainsKey(capturedEvent.EventName + capturedEvent.ComplexName))
            {
                return _EventTypeExpireTime[capturedEvent.EventName + capturedEvent.ComplexName];
            }

            //singluar events will not have a time out period so give them a default one.
            return new TimeSpan(0, 0, 0, 0, 100);
            //throw new Exception("There is no expirery Time set for the event type:" + capturedEvent.EventName);
        }

        //TODO: put expire_event(int position) method here.

        //todo: subscribe to eventInstance Expirery here.

        #endregion

        #region Member Variables

        XmlDocument _rulesFile = new XmlDocument();
        XmlDocument _xomlFile = new XmlDocument();
        //XmlDocument _xmlRules = new XmlDocument();

        Dictionary<String, TimeSpan> _EventTypeExpireTime = new Dictionary<String, TimeSpan>();
        List<IComplexEvent> _complexEventList = new List<IComplexEvent>();
        List<ICapturedEvent> _queue = new List<ICapturedEvent>();
        List<ICapturedEvent> _newQueue = new List<ICapturedEvent>();
        Type _type;
        Dictionary<string, object> _items;

        Boolean _workFlowBusy = false;
        Boolean _restartworkflow = false;
        WorkflowRuntime _workflowRuntime = new WorkflowRuntime();
        WorkflowInstance _workflowInstance = null;

        #endregion

    }
}
///<Summary>
///An example of loading .rules files for execution is avaliable at: http://blogs.msdn.com/richardbpi/archive/2006/10/07/Windows-Workflow-Rules-From-Outside-A-Workflow_2C00_-And-Other-Conference-Demos.aspx
///That example was used to help load rules dynamically at runtime as they are created.
///</Summary>